<?php

class AuthHelper {

    //config
    private $sitekey;
    private $smarty = null;

    public function __construct() {
        include_once($_SERVER['DOCUMENT_ROOT'] . "/RecJP/class/DAL/DAL.Authenticate.php");
        include_once($_SERVER['DOCUMENT_ROOT'] . "/RecJP/inc/smarty_inc.php");
        include_once($_SERVER['DOCUMENT_ROOT'] . "/RecJP/class/login_result_enum.php");
        $this->smarty = getSmarty();
        $this->sitekey = '798EB23F-4D80-4B17-BE63-889A28D5579E'; //随机guid, 不要修改!!!
    }

    public function Login($email, $password) {
        $selection = DAL_Authenticate::SelectUser($email, $password);

        if (count($selection)>0) {
            $password = $selection[0]['user_salt'] . $password;
            $password = $this->hashData($password);

            $match = ($password == $selection[0]['password']);
            $is_active = (boolean) $selection[0]['is_active'];
            $verified = (boolean) $selection[0]['is_verified'];

            if ($match == true) {
                if ($is_active == true) {
                    if ($verified == true) {
                        $token = $this->createUserToken();

                        //设定session变量之前开启session;
                        //session_start();
                        $_SESSION['token'] = $token;
                        $_SESSION['userid'] = $selection[0]['id'];

                        //在logged_in_member表中插入记录
                        $inserted = $this->LogMemberIn($selection[0]['id'], $token);

                        //终于可以登录了
                        if ($inserted != false) {
                            $this->LoginSuccess($selection[0]);
                            return LoingResult::Success;
                        }

                        return LoingResult::UnknownError;
                    } else {
                        return LoingResult::NotVerified;
                    }
                } else {
                    return LoingResult::NotActive;
                }
            }
        } else {
            return LoingResult::NoMatch;
        }
    }

    //这里的开销如果在大并发环境下可能会成为一个问题. 解决办法就是不要去数据库里查询了, 直接判断客户端的某些标识信息
    //诸如user_agent或者user_ip等. 这种方法的通常实现是将agent和ip组合起来并且进行一次hash, 然后存进
    //session字典中后续访问通过判断键值从而达到检测的目的. 其实这种方式有一定的弊端, 原因在于agent和ip都是由客
    //户端提供的, agent也可以伪造, ip暂时看作不能伪造. 这就造成了攻击者可以通过伪造适当的agent来使hash匹配的
    //情况产生. 所以, 其实完全可以采用更简单更安全的做法, agent和ip分别存入session字典中后续再分别验证即可.
    public function checkSession() {
        $userid = $_SESSION['userid'];
        $selection = DAL_Authenticate::SelectUserVisitLog($userid);

        if ($selection[0] != null) {
            if (session_id() == $selection[0]['session_id'] &&
                    $_SESSION['token'] == $selection[0]['token']) {
                //之所以要再一次的refresh, 是为了防止一种叫做session fixation的攻击
                return $this->refreshSession();
            }
        }
        return false;
    }

    public function LoginFailed() {
        session_destroy();
        //$this->smarty->display('Login.tpl.htm');
        header("location: Login.php");
        exit;
    }

    public function LoginSuccess($user) {
        $_SESSION['userid'] = $user['id'];
        $_SESSION['username'] = $user['email'];
    }

    public function Logout() {
        DAL_Authenticate::DeleteUserVisitLog(session_id());
        session_destroy();
        $this->smarty->display('Login.tpl.htm');
        exit;
    }

    public function isAdmin() {
        //todo: 从数据库读取逻辑
        if ($selection['is_admin'] == 1) {
            return true;
        }

        return false;
    }

    public function createUser($email, $password, $is_admin = 0) {
        $user_salt = $this->randomString();
        //todo: 此处逻辑可以提取出来, 因为如何组合password是一个易变的逻辑
        $password = $user_salt . $password;
        $password = $this->hashData($password);

        //生成验证邮件用的验证码
        $code = $this->randomString(80);

        $created = DAL_Authenticate::CreateUser($email, $password, $code, $user_salt);

        if ($created != false) {
            $this->sendVerification(); //创建成功, 发送验证邮件.
            return true;
        }

        return false;
    }

    protected function LogMemberIn($userid, $token) {
        //先获取当前上下文中的sessionid
        $sessionid = session_id();

        //删掉之前的访问记录
        DAL_Authenticate::DeleteUserVisitLog($userid, $sessionid);

        //插入最新的访问记录
        $insert = DAL_Authenticate::InsertUserVisitLog($sessionid, $userid, $token);

        return $insert;
    }

    protected function createUserToken() {
        $random = $this->randomString();
        //关于是使用agent还是ip. agent可以被伪造, 但是因为整个token的建立还基于了一部分的随机字符串, 所以整个方法还是安全的.
        //ip本身来说相对的不容易被伪造, 我们可以认为每次相同ip的访问都是来自同一个终端. 在这里用ip和agent的区别不大, 都是可以的.
        $token = $_SERVER['HTTP_USER_AGENT'] . $random;
        $token = $this->hashData($token);

        return $token;
    }

    protected function hashData($data) {
        //todo: 这个hash可能过弱了.
        return hash_hmac('sha512', $data, $this->sitekey);
    }

    protected function sendVerification() {
        //todo: 此处添加逻辑
    }

    private function refreshSession() {
        //拿到旧有的id, 会用作update的查询条件
        $userid = $_SESSION['userid'];

        //重新生成会话id
        session_regenerate_id();

        //重新生成令牌
        $token = $this->createUserToken();

        //更新掉令牌
        $_SESSION['token'] = $token;

        return DAL_Authenticate::UpdateUserVisitLog($userid, session_id(), $token); //已经是新的session_id了
    }

    private function randomString($length = 50) {
        $characters = '0123456789abcdefghijklmnopqrstuvwxyz';
        $string = '';

        for ($p = 0; $p < $length; $p++) {
            $string .= $characters[mt_rand(0, strlen($characters) - 1)];
        }

        return $string;
    }

}

?>
